package com.lzl.logdesenshelper.aspect;

import com.alibaba.fastjson.JSON;
import com.lzl.logdesenshelper.annotation.LogField;
import com.lzl.logdesenshelper.annotation.LogMessage;
import com.lzl.logdesenshelper.common.ErrorEnum;
import com.lzl.logdesenshelper.common.Response;
import com.lzl.logdesenshelper.utils.AspectMethodHelper;
import com.lzl.logdesenshelper.utils.SensitiveValueFilter;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.helpers.MessageFormatter;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.lang.NonNull;
import org.springframework.util.Assert;
import org.springframework.util.StopWatch;
import org.springframework.util.StringUtils;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;

import static com.lzl.logdesenshelper.utils.DesensitizationUtil.desentizeParameter;


@Slf4j
@Aspect
public class LogMessageAspect {

    @Around("@annotation(com.lzl.logdesenshelper.annotation.LogMessage)")
    public Object around(@NonNull ProceedingJoinPoint point) throws Throwable {
        Method method = AspectMethodHelper.getMethod(point);
        LogMessage ann = AnnotationUtils.findAnnotation(method, LogMessage.class);
        Assert.notNull(ann, "Cannot found annotation @LogMessage.");
        LogInfo<String> logInfo = new LogInfo<>();

        setCalledTemplate(logInfo, method);

        setDetailTemplate(logInfo, ann);

        setParamsTemplate(logInfo, method, point.getArgs());

        long start = System.currentTimeMillis();

        Object result = called(point, ann);

        long end = System.currentTimeMillis();

        setReturnTemplate(logInfo, ann, result);

        setExecuteTime(logInfo, start, end);

        log.info(format(logInfo.getTemplate(), logInfo.getArgs()));
        return result;
    }

    private Object called(ProceedingJoinPoint point, LogMessage ann) throws Throwable {
        try {
            return point.proceed(point.getArgs());
        } catch (Exception e) {
            if (ann.dealErrorMsg()) {
                log.error("[proceed error]",e);
                return Response.ofFail(ErrorEnum.SYSTEM_ERROR);
            }
            throw e;
        }
    }

    private void setCalledTemplate(LogInfo<String> logInfo, Method method) {
        logInfo.appendTemplate("[{}]");
        logInfo.addArgs(method.getDeclaringClass().getCanonicalName() + '#' + method.getName());
    }

    private void setDetailTemplate(LogInfo<String> logInfo, LogMessage ann) {
        if (StringUtils.hasText(ann.value())) {
            logInfo.appendTemplate(" [{}]");
            logInfo.addArgs(ann.value());
        }
    }

    private void setParamsTemplate(LogInfo<String> logInfo, Method method, Object[] args) {
        LogMessage ann = AnnotationUtils.findAnnotation(method, LogMessage.class);
        Assert.notNull(ann, "Cannot found annotation @LogMessage.");
        if (!ann.ignoreParams()) {
            logInfo.appendTemplate(" args=({})");
            StringBuilder sb = new StringBuilder();
            Annotation[][] annotations = method.getParameterAnnotations();
            for (int i = 0; i < annotations.length; i++) {
                boolean logFieldFlag = false;
                Object param = args[i];
                Annotation[] paramAnn = annotations[i];
                if (param == null || paramAnn.length == 0) {
                    continue;
                }
                for (Annotation annotation : paramAnn) {
                    if (annotation.annotationType().equals(LogField.class)) {
                        LogField logField = (LogField) annotation;
                        String parameter = desentizeParameter(param, logField);
                        sb.append(parameter).append(",");
                        logFieldFlag = true;
                        break;
                    }
                }
                if (!logFieldFlag) {
                    String jsonString = JSON.toJSONString(param, new SensitiveValueFilter());
                    sb.append(jsonString).append(",");
                }
            }
            logInfo.addArgs(sb.length() == 0 ? "" : sb.substring(0, sb.length() - 1));
        }
    }

    private void setReturnTemplate(LogInfo<String> logInfo, LogMessage ann, Object result) {
        if (!ann.ignoreReturn()) {
            logInfo.appendTemplate(" return=({})");
            String jsonString = JSON.toJSONString(result, new SensitiveValueFilter());
            logInfo.addArgs(jsonString);
        }
    }

    private void setExecuteTime(LogInfo<String> logInfo, Long start, Long end) {
        logInfo.appendTemplate(" 耗时：({}毫秒)");
        logInfo.addArgs(String.valueOf(end - start));
    }

    /**
     * LogInfo
     */
    private static class LogInfo<T> {

        @Getter
        private String template = "";

        private final List<T> args = new LinkedList<>();


        void appendTemplate(String template) {
            this.template += template;
        }

        void addArgs(T arg) {
            this.args.add(arg);
        }

        Object[] getArgs() {
            return this.args.toArray();
        }
    }

    private String format(String message, Object... args) {
        if (Objects.isNull(message) || message.trim().isEmpty()) {
            return message;
        }
        return MessageFormatter.arrayFormat(message, args).getMessage();
    }
}
